 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.internal.editors.text;

 import java.util.ArrayList ;
 import java.util.Arrays ;
 import java.util.Iterator ;
 import java.util.List ;

 import org.osgi.service.prefs.BackingStoreException;

 import org.eclipse.core.runtime.Assert;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.preferences.InstanceScope;

 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.graphics.FontMetrics;
 import org.eclipse.swt.graphics.GC;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.graphics.RGB;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Label;


 import org.eclipse.ui.editors.text.EditorsUI;

 import org.eclipse.jface.dialogs.Dialog;
 import org.eclipse.jface.preference.ColorSelector;
 import org.eclipse.jface.preference.PreferenceConverter;
 import org.eclipse.jface.resource.JFaceResources;
 import org.eclipse.jface.viewers.ArrayContentProvider;
 import org.eclipse.jface.viewers.ComboViewer;
 import org.eclipse.jface.viewers.ISelectionChangedListener;
 import org.eclipse.jface.viewers.IStructuredContentProvider;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.jface.viewers.LabelProvider;
 import org.eclipse.jface.viewers.SelectionChangedEvent;
 import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.jface.viewers.StructuredViewer;
 import org.eclipse.jface.viewers.TableViewer;
 import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.ui.texteditor.AnnotationPreference;
 import org.eclipse.ui.texteditor.MarkerAnnotationPreferences;

 /**
  * Configures the linked mode preferences. The preferences belong to
  * org.eclipse.ui.editors. However, as they are chiefly used in the java editor,
  * we keep the preferences here for the time being.
  *
  * @since 3.2 (in jdt.ui since 3.1)
  */
 class LinkedModeConfigurationBlock implements IPreferenceConfigurationBlock {

     private static final String EXIT= "org.eclipse.ui.internal.workbench.texteditor.link.exit"; //$NON-NLS-1$
 private static final String TARGET= "org.eclipse.ui.internal.workbench.texteditor.link.target"; //$NON-NLS-1$
 private static final String MASTER= "org.eclipse.ui.internal.workbench.texteditor.link.master"; //$NON-NLS-1$
 private static final String SLAVE= "org.eclipse.ui.internal.workbench.texteditor.link.slave"; //$NON-NLS-1$

     private static final class ListItem {
         final String label;
         final Image image;
         final String colorKey;
         final String highlightKey;
         final String overviewRulerKey;
         final String textStyleKey;
         final String textKey;
         final String verticalRulerKey;
         final List validStyles;

         ListItem(String label, Image image, String colorKey, String textKey, String overviewRulerKey, String highlightKey, String verticalRulerKey, String textStyleKey, List validStyles) {
             this.label= label;
             this.image= image;
             this.colorKey= colorKey;
             this.highlightKey= highlightKey;
             this.overviewRulerKey= overviewRulerKey;
             this.textKey= textKey;
             this.textStyleKey= textStyleKey;
             this.verticalRulerKey= verticalRulerKey;
             this.validStyles= validStyles;
         }
     }
     
     private static final class ItemContentProvider implements IStructuredContentProvider {

         public Object [] getElements(Object inputElement) {
             return (ListItem[]) inputElement;
         }

         public void dispose() {
         }

         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
         }
     }
     
     private final class ItemLabelProvider extends LabelProvider {
         
         public String getText(Object element) {
             return ((ListItem) element).label;
         }
     }
     
     private static class ArrayLabelProvider extends LabelProvider {
         public String getText(Object element) {
             return ((String []) element)[0].toString();
         }
     }
     
     final static String [] HIGHLIGHT= new String [] {TextEditorMessages.LinkedModeConfigurationBlock_HIGHLIGHT, "unused"}; //$NON-NLS-1$
 final static String [] UNDERLINE= new String [] {TextEditorMessages.LinkedModeConfigurationBlock_UNDERLINE, AnnotationPreference.STYLE_UNDERLINE};
     final static String [] BOX= new String [] {TextEditorMessages.LinkedModeConfigurationBlock_BOX, AnnotationPreference.STYLE_BOX};
     final static String [] DASHED_BOX= new String [] {TextEditorMessages.LinkedModeConfigurationBlock_DASHED_BOX, AnnotationPreference.STYLE_DASHED_BOX};
     final static String [] IBEAM= new String [] {TextEditorMessages.LinkedModeConfigurationBlock_IBEAM, AnnotationPreference.STYLE_IBEAM};
     final static String [] SQUIGGLES= new String [] {TextEditorMessages.LinkedModeConfigurationBlock_SQUIGGLES, AnnotationPreference.STYLE_SQUIGGLES};

     private ColorSelector fAnnotationForegroundColorEditor;

     private Button fShowInTextCheckBox;
     
     private StructuredViewer fAnnotationTypeViewer;
     private final ListItem[] fListModel;

     private ComboViewer fDecorationViewer;
     private FontMetrics fFontMetrics;
     protected static final int INDENT= 20;
     private OverlayPreferenceStore fStore;
     
     private ArrayList fMasterSlaveListeners= new ArrayList ();
     
     private OverlayPreferenceStore getPreferenceStore() {
         return fStore;
     }
     
     public LinkedModeConfigurationBlock(OverlayPreferenceStore store) {
         fStore= store;
         final MarkerAnnotationPreferences prefs= EditorsPlugin.getDefault().getMarkerAnnotationPreferences();
         getPreferenceStore().addKeys(createOverlayStoreKeys(prefs));
         fListModel= createAnnotationTypeListModel(prefs);
     }

     private OverlayPreferenceStore.OverlayKey[] createOverlayStoreKeys(MarkerAnnotationPreferences preferences) {
         ArrayList overlayKeys= new ArrayList ();

         Iterator e= preferences.getAnnotationPreferences().iterator();
         while (e.hasNext()) {
             AnnotationPreference info= (AnnotationPreference) e.next();
             
             if (isLinkedModeAnnotation(info)) {
                 overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, info.getColorPreferenceKey()));
                 overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, info.getTextPreferenceKey()));
                 overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, info.getTextStylePreferenceKey()));
                 overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, info.getHighlightPreferenceKey()));
             }
         }
         
         OverlayPreferenceStore.OverlayKey[] keys= new OverlayPreferenceStore.OverlayKey[overlayKeys.size()];
         overlayKeys.toArray(keys);
         return keys;
     }

     private boolean isLinkedModeAnnotation(AnnotationPreference info) {
         final Object type= info.getAnnotationType();
         return type.equals(MASTER)
                 || (type.equals(SLAVE))
                 || (type.equals(TARGET))
                 || (type.equals(EXIT));
     }
     
     private ListItem[] createAnnotationTypeListModel(MarkerAnnotationPreferences preferences) {
         ArrayList listModelItems= new ArrayList ();
         Iterator e= preferences.getAnnotationPreferences().iterator();
         
         while (e.hasNext()) {
             AnnotationPreference info= (AnnotationPreference) e.next();
             if (isLinkedModeAnnotation(info)) {
                 String label= info.getPreferenceLabel();
                 List styles= getStyles(info.getAnnotationType());
                 listModelItems.add(new ListItem(label, null, info.getColorPreferenceKey(), info.getTextPreferenceKey(), info.getOverviewRulerPreferenceKey(), info.getHighlightPreferenceKey(), info.getVerticalRulerPreferenceKey(), info.getTextStylePreferenceKey(), styles));
             }
         }
         
         ListItem[] items= new ListItem[listModelItems.size()];
         listModelItems.toArray(items);
         return items;
     }
     

     private List getStyles(Object type) {
         if (type.equals(MASTER))
             return Arrays.asList(new String [][] {BOX, DASHED_BOX, HIGHLIGHT, UNDERLINE, SQUIGGLES});
         if (type.equals(SLAVE))
             return Arrays.asList(new String [][] {BOX, DASHED_BOX, HIGHLIGHT, UNDERLINE, SQUIGGLES});
         if (type.equals(TARGET))
             return Arrays.asList(new String [][] {BOX, DASHED_BOX, HIGHLIGHT, UNDERLINE, SQUIGGLES});
         if (type.equals(EXIT))
             return Arrays.asList(new String [][] {IBEAM});
         return new ArrayList ();
     }

     /**
      * Creates page for hover preferences.
      *
      * @param parent the parent composite
      * @return the control for the preference page
      */
     public Control createControl(Composite parent) {
         OverlayPreferenceStore store= getPreferenceStore();
         store.load();
         store.start();
         
         initializeDialogUnits(parent);

         Composite composite= new Composite(parent, SWT.NONE);
         GridLayout layout= new GridLayout();
         layout.marginHeight= 0;
         layout.marginWidth= 0;
         composite.setLayout(layout);

         Label label= new Label(composite, SWT.LEFT);
         label.setText(TextEditorMessages.LinkedModeConfigurationBlock_annotationPresentationOptions);
         GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_FILL);
         label.setLayoutData(gd);

         Composite editorComposite= new Composite(composite, SWT.NONE);
         layout= new GridLayout();
         layout.numColumns= 2;
         layout.marginHeight= 0;
         layout.marginWidth= 0;
         editorComposite.setLayout(layout);
         gd= new GridData(SWT.FILL, SWT.FILL, true, true);
         editorComposite.setLayoutData(gd);

         fAnnotationTypeViewer= new TableViewer(editorComposite, SWT.SINGLE | SWT.V_SCROLL | SWT.BORDER);
         fAnnotationTypeViewer.setLabelProvider(new ItemLabelProvider());
         fAnnotationTypeViewer.setContentProvider(new ItemContentProvider());
         gd= new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false);
         gd.heightHint= convertHeightInCharsToPixels(5);
         fAnnotationTypeViewer.getControl().setLayoutData(gd);
         
         Composite optionsComposite= new Composite(editorComposite, SWT.NONE);
         layout= new GridLayout();
         layout.marginHeight= 0;
         layout.marginWidth= 0;
         layout.numColumns= 2;
         optionsComposite.setLayout(layout);
         optionsComposite.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false));
         
         // we only allow to set either "show in text" or "highlight in text", but not both

         fShowInTextCheckBox= new Button(optionsComposite, SWT.CHECK);
         fShowInTextCheckBox.setText(TextEditorMessages.LinkedModeConfigurationBlock_labels_showIn);
         gd= new GridData(GridData.FILL_HORIZONTAL);
         gd.horizontalAlignment= GridData.BEGINNING;
         fShowInTextCheckBox.setLayoutData(gd);
         
         fDecorationViewer= new ComboViewer(optionsComposite, SWT.READ_ONLY);
         fDecorationViewer.setContentProvider(new ArrayContentProvider());
         fDecorationViewer.setLabelProvider(new ArrayLabelProvider());
         gd= new GridData(GridData.FILL_HORIZONTAL);
         gd.horizontalAlignment= GridData.BEGINNING;
         fDecorationViewer.getControl().setLayoutData(gd);
         fDecorationViewer.setInput(new Object [] {HIGHLIGHT, SQUIGGLES, BOX, DASHED_BOX, UNDERLINE, IBEAM});
         
         label= new Label(optionsComposite, SWT.LEFT);
         label.setText(TextEditorMessages.LinkedModeConfigurationBlock_color);
         gd= new GridData();
         gd.horizontalAlignment= GridData.BEGINNING;
         label.setLayoutData(gd);

         fAnnotationForegroundColorEditor= new ColorSelector(optionsComposite);
         Button foregroundColorButton= fAnnotationForegroundColorEditor.getButton();
         gd= new GridData(GridData.FILL_HORIZONTAL);
         gd.horizontalAlignment= GridData.BEGINNING;
         foregroundColorButton.setLayoutData(gd);
         
         createDependency(fShowInTextCheckBox, new Control[] {label, foregroundColorButton});

         fAnnotationTypeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
             public void selectionChanged(SelectionChangedEvent event) {
                 handleAnnotationListSelection();
             }
         });
         
         fShowInTextCheckBox.addSelectionListener(new SelectionListener() {
             public void widgetDefaultSelected(SelectionEvent e) {
                 // do nothing
 }
             
             public void widgetSelected(SelectionEvent e) {
                 ListItem item= getSelectedItem();
                 final boolean value= fShowInTextCheckBox.getSelection();
                 if (value) {
                     // enable whatever is in the combo
 String [] decoration= (String []) ((IStructuredSelection) fDecorationViewer.getSelection()).getFirstElement();
                     if (HIGHLIGHT.equals(decoration))
                         getPreferenceStore().setValue(item.highlightKey, true);
                     else
                         getPreferenceStore().setValue(item.textKey, true);
                 } else {
                     // disable both
 getPreferenceStore().setValue(item.textKey, false);
                     getPreferenceStore().setValue(item.highlightKey, false);
                 }
                 getPreferenceStore().setValue(item.textKey, value);
                 updateDecorationViewer(item, false);
             }
         });
         
         foregroundColorButton.addSelectionListener(new SelectionListener() {
             public void widgetDefaultSelected(SelectionEvent e) {
                 // do nothing
 }
             
             public void widgetSelected(SelectionEvent e) {
                 ListItem item= getSelectedItem();
                 PreferenceConverter.setValue(getPreferenceStore(), item.colorKey, fAnnotationForegroundColorEditor.getColorValue());
             }
         });
         
         fDecorationViewer.addSelectionChangedListener(new ISelectionChangedListener() {

             /*
              * @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
              */
             public void selectionChanged(SelectionChangedEvent event) {
                 String [] decoration= (String []) ((IStructuredSelection) fDecorationViewer.getSelection()).getFirstElement();
                 ListItem item= getSelectedItem();
                 
                 if (fShowInTextCheckBox.getSelection()) {
                     if (HIGHLIGHT.equals(decoration)) {
                         getPreferenceStore().setValue(item.highlightKey, true);
                         getPreferenceStore().setValue(item.textKey, false);
                         getPreferenceStore().setValue(item.textStyleKey, AnnotationPreference.STYLE_NONE);
                     } else {
                         getPreferenceStore().setValue(item.highlightKey, false);
                         getPreferenceStore().setValue(item.textKey, true);
                         getPreferenceStore().setValue(item.textStyleKey, decoration[1]);
                     }
                 }
             }
         });
         
         return composite;

     }
     
     /**
      * Returns the number of pixels corresponding to the width of the given
      * number of characters.
      * <p>
      * This method may only be called after <code>initializeDialogUnits</code>
      * has been called.
      * </p>
      * <p>
      * Clients may call this framework method, but should not override it.
      * </p>
      *
      * @param chars
      * the number of characters
      * @return the number of pixels
      */
     protected int convertWidthInCharsToPixels(int chars) {
         // test for failure to initialize for backward compatibility
 if (fFontMetrics == null)
             return 0;
         return Dialog.convertWidthInCharsToPixels(fFontMetrics, chars);
     }

     /**
      * Returns the number of pixels corresponding to the height of the given
      * number of characters.
      * <p>
      * This method may only be called after <code>initializeDialogUnits</code>
      * has been called.
      * </p>
      * <p>
      * Clients may call this framework method, but should not override it.
      * </p>
      *
      * @param chars
      * the number of characters
      * @return the number of pixels
      */
     protected int convertHeightInCharsToPixels(int chars) {
         // test for failure to initialize for backward compatibility
 if (fFontMetrics == null)
             return 0;
         return Dialog.convertHeightInCharsToPixels(fFontMetrics, chars);
     }
     /**
      * Initializes the computation of horizontal and vertical dialog units based
      * on the size of current font.
      * <p>
      * This method must be called before any of the dialog unit based conversion
      * methods are called.
      * </p>
      *
      * @param testControl
      * a control from which to obtain the current font
      */
     protected void initializeDialogUnits(Control testControl) {
         // Compute and store a font metric
 GC gc = new GC(testControl);
         gc.setFont(JFaceResources.getDialogFont());
         fFontMetrics = gc.getFontMetrics();
         gc.dispose();
     }
     
     private void handleAnnotationListSelection() {
         ListItem item= getSelectedItem();
         
         RGB rgb= PreferenceConverter.getColor(getPreferenceStore(), item.colorKey);
         fAnnotationForegroundColorEditor.setColorValue(rgb);
         
         boolean highlight= item.highlightKey == null ? false : getPreferenceStore().getBoolean(item.highlightKey);
         boolean showInText = item.textKey == null ? false : getPreferenceStore().getBoolean(item.textKey);
         fShowInTextCheckBox.setSelection(showInText || highlight);
         
         updateDecorationViewer(item, true);
     }
     
     /*
      * @see org.eclipse.jdt.internal.ui.preferences.IPreferenceConfigurationBlock#initialize()
      */
     public void initialize() {
         initializeFields();
         
         fAnnotationTypeViewer.setInput(fListModel);
         fAnnotationTypeViewer.getControl().getDisplay().asyncExec(new Runnable () {
             public void run() {
                 if (fAnnotationTypeViewer != null && !fAnnotationTypeViewer.getControl().isDisposed()) {
                     fAnnotationTypeViewer.setSelection(new StructuredSelection(fListModel[0]));
                     initializeFields();
                 }
             }
         });
     }

     private ListItem getSelectedItem() {
         return (ListItem) ((IStructuredSelection) fAnnotationTypeViewer.getSelection()).getFirstElement();
     }

     private void updateDecorationViewer(ListItem item, boolean changed) {
         // decoration selection: if the checkbox is enabled, there is
 // only one case where the combo is not enabled: if both the highlight and textStyle keys are null
 final boolean enabled= fShowInTextCheckBox.getSelection() && !(item.highlightKey == null && item.textStyleKey == null);
         fDecorationViewer.getControl().setEnabled(enabled);
         
         if (changed) {
             String [] selection= null;
             ArrayList list= new ArrayList ();
             
             list.addAll(item.validStyles);
             
             if (getPreferenceStore().getBoolean(item.highlightKey))
                 selection= HIGHLIGHT;
             
             // set selection
 if (selection == null) {
                 String val= getPreferenceStore().getString(item.textStyleKey);
                 for (Iterator iter= list.iterator(); iter.hasNext();) {
                     String [] elem= (String []) iter.next();
                     if (elem[1].equals(val)) {
                         selection= elem;
                         break;
                     }
                 }
             }
             
             fDecorationViewer.setInput(list.toArray(new Object [list.size()]));
             if (selection == null)
                 selection= (String []) list.get(0);
             fDecorationViewer.setSelection(new StructuredSelection((Object ) selection), true);
         }
     }
     
     
     public void performOk() {
         getPreferenceStore().propagate();
         
         try {
             Platform.getPreferencesService().getRootNode().node(InstanceScope.SCOPE).node(EditorsUI.PLUGIN_ID).flush();
         } catch (BackingStoreException e) {
             EditorsPlugin.log(e);
         }
     }
     
     
     public void performDefaults() {
         getPreferenceStore().loadDefaults();
         
         /*
          * Only call super after updating fShowInTextCheckBox, so that
          * the master-slave dependencies get properly updated.
          */
         handleAnnotationListSelection();

         initializeFields();
     }
     
     public void dispose() {
         OverlayPreferenceStore store= getPreferenceStore();
         if (store != null) {
             store.stop();
         }
     }

     protected void createDependency(final Button master, final Control slave) {
         createDependency(master, new Control[] {slave});
     }
     
     protected void createDependency(final Button master, final Control[] slaves) {
         Assert.isTrue(slaves.length > 0);
         indent(slaves[0]);
         SelectionListener listener= new SelectionListener() {
             public void widgetSelected(SelectionEvent e) {
                 boolean state= master.getSelection();
                 for (int i= 0; i < slaves.length; i++) {
                     slaves[i].setEnabled(state);
                 }
             }

             public void widgetDefaultSelected(SelectionEvent e) {}
         };
         master.addSelectionListener(listener);
         fMasterSlaveListeners.add(listener);
     }

     protected static void indent(Control control) {
         ((GridData) control.getLayoutData()).horizontalIndent+= INDENT;
     }

     private void initializeFields() {
         
         // Update slaves
 Iterator iter= fMasterSlaveListeners.iterator();
         while (iter.hasNext()) {
             SelectionListener listener= (SelectionListener)iter.next();
             listener.widgetSelected(null);
         }
     }

     /*
      * @see org.eclipse.ui.internal.editors.text.IPreferenceConfigurationBlock#canPerformOk()
      */
     public boolean canPerformOk() {
         return true;
     }

 }

